import { Game_CharacterBase } from "@/objects/GameCharacterBase.js";
import { contains } from "@/utils/Array.js";
import { AudioManager } from "@/managers/AudioManager.js";

export class Game_Character extends Game_CharacterBase {
    static ROUTE_END               = 0;
    static ROUTE_MOVE_DOWN         = 1;
    static ROUTE_MOVE_LEFT         = 2;
    static ROUTE_MOVE_RIGHT        = 3;
    static ROUTE_MOVE_UP           = 4;
    static ROUTE_MOVE_LOWER_L      = 5;
    static ROUTE_MOVE_LOWER_R      = 6;
    static ROUTE_MOVE_UPPER_L      = 7;
    static ROUTE_MOVE_UPPER_R      = 8;
    static ROUTE_MOVE_RANDOM       = 9;
    static ROUTE_MOVE_TOWARD       = 10;
    static ROUTE_MOVE_AWAY         = 11;
    static ROUTE_MOVE_FORWARD      = 12;
    static ROUTE_MOVE_BACKWARD     = 13;
    static ROUTE_JUMP              = 14;
    static ROUTE_WAIT              = 15;
    static ROUTE_TURN_DOWN         = 16;
    static ROUTE_TURN_LEFT         = 17;
    static ROUTE_TURN_RIGHT        = 18;
    static ROUTE_TURN_UP           = 19;
    static ROUTE_TURN_90D_R        = 20;
    static ROUTE_TURN_90D_L        = 21;
    static ROUTE_TURN_180D         = 22;
    static ROUTE_TURN_90D_R_L      = 23;
    static ROUTE_TURN_RANDOM       = 24;
    static ROUTE_TURN_TOWARD       = 25;
    static ROUTE_TURN_AWAY         = 26;
    static ROUTE_SWITCH_ON         = 27;
    static ROUTE_SWITCH_OFF        = 28;
    static ROUTE_CHANGE_SPEED      = 29;
    static ROUTE_CHANGE_FREQ       = 30;
    static ROUTE_WALK_ANIME_ON     = 31;
    static ROUTE_WALK_ANIME_OFF    = 32;
    static ROUTE_STEP_ANIME_ON     = 33;
    static ROUTE_STEP_ANIME_OFF    = 34;
    static ROUTE_DIR_FIX_ON        = 35;
    static ROUTE_DIR_FIX_OFF       = 36;
    static ROUTE_THROUGH_ON        = 37;
    static ROUTE_THROUGH_OFF       = 38;
    static ROUTE_TRANSPARENT_ON    = 39;
    static ROUTE_TRANSPARENT_OFF   = 40;
    static ROUTE_CHANGE_IMAGE      = 41;
    static ROUTE_CHANGE_OPACITY    = 42;
    static ROUTE_CHANGE_BLEND_MODE = 43;
    static ROUTE_PLAY_SE           = 44;
    static ROUTE_SCRIPT            = 45;
    constructor() {
        super();
        this._moveRouteForcing = false;
        this._moveRoute = null;
        this._moveRouteIndex = 0;
        this._originalMoveRoute = null;
        this._originalMoveRouteIndex = 0;
        this._waitCount = 0;
    }

    memorizeMoveRoute() {
        this._originalMoveRoute       = this._moveRoute;
        this._originalMoveRouteIndex  = this._moveRouteIndex;
    }

    restoreMoveRoute() {
        this._moveRoute          = this._originalMoveRoute;
        this._moveRouteIndex     = this._originalMoveRouteIndex;
        this._originalMoveRoute  = null;
    }

    isMoveRouteForcing() {
        return this._moveRouteForcing;
    }

    setMoveRoute(moveRoute) {
        this._moveRoute = moveRoute;
        this._moveRouteIndex = 0;
        this._moveRouteForcing = false;
    }

    forceMoveRoute(moveRoute) {
        if (!this._originalMoveRoute) {
            this.memorizeMoveRoute();
        }
        this._moveRoute = moveRoute;
        this._moveRouteIndex = 0;
        this._moveRouteForcing = true;
        this._waitCount = 0;
    }

    updateStop() {
        super.updateStop();
        if (this._moveRouteForcing) {
            this.updateRoutineMove();
        }
    }

    updateRoutineMove() {
        if (this._waitCount > 0) {
            this._waitCount--;
        } else {
            this.setMovementSuccess(true);
            let command = this._moveRoute.list[this._moveRouteIndex];
            if (command) {
                this.processMoveCommand(command);
                this.advanceMoveRouteIndex();
            }
        }
    }

    processMoveCommand(command) {
        let gc = Game_Character;
        let params = command.parameters;
        switch (command.code) {
        case gc.ROUTE_END:
            this.processRouteEnd();
            break;
        case gc.ROUTE_MOVE_DOWN:
            this.moveStraight(2);
            break;
        case gc.ROUTE_MOVE_LEFT:
            this.moveStraight(4);
            break;
        case gc.ROUTE_MOVE_RIGHT:
            this.moveStraight(6);
            break;
        case gc.ROUTE_MOVE_UP:
            this.moveStraight(8);
            break;
        case gc.ROUTE_MOVE_LOWER_L:
            this.moveDiagonally(4, 2);
            break;
        case gc.ROUTE_MOVE_LOWER_R:
            this.moveDiagonally(6, 2);
            break;
        case gc.ROUTE_MOVE_UPPER_L:
            this.moveDiagonally(4, 8);
            break;
        case gc.ROUTE_MOVE_UPPER_R:
            this.moveDiagonally(6, 8);
            break;
        case gc.ROUTE_MOVE_RANDOM:
            this.moveRandom();
            break;
        case gc.ROUTE_MOVE_TOWARD:
            this.moveTowardPlayer();
            break;
        case gc.ROUTE_MOVE_AWAY:
            this.moveAwayFromPlayer();
            break;
        case gc.ROUTE_MOVE_FORWARD:
            this.moveForward();
            break;
        case gc.ROUTE_MOVE_BACKWARD:
            this.moveBackward();
            break;
        case gc.ROUTE_JUMP:
            this.jump(params[0], params[1]);
            break;
        case gc.ROUTE_WAIT:
            this._waitCount = params[0] - 1;
            break;
        case gc.ROUTE_TURN_DOWN:
            this.setDirection(2);
            break;
        case gc.ROUTE_TURN_LEFT:
            this.setDirection(4);
            break;
        case gc.ROUTE_TURN_RIGHT:
            this.setDirection(6);
            break;
        case gc.ROUTE_TURN_UP:
            this.setDirection(8);
            break;
        case gc.ROUTE_TURN_90D_R:
            this.turnRight90();
            break;
        case gc.ROUTE_TURN_90D_L:
            this.turnLeft90();
            break;
        case gc.ROUTE_TURN_180D:
            this.turn180();
            break;
        case gc.ROUTE_TURN_90D_R_L:
            this.turnRightOrLeft90();
            break;
        case gc.ROUTE_TURN_RANDOM:
            this.turnRandom();
            break;
        case gc.ROUTE_TURN_TOWARD:
            this.turnTowardPlayer();
            break;
        case gc.ROUTE_TURN_AWAY:
            this.turnAwayFromPlayer();
            break;
        case gc.ROUTE_SWITCH_ON:
            $gameSwitches.setValue(params[0], true);
            break;
        case gc.ROUTE_SWITCH_OFF:
            $gameSwitches.setValue(params[0], false);
            break;
        case gc.ROUTE_CHANGE_SPEED:
            this.setMoveSpeed(params[0]);
            break;
        case gc.ROUTE_CHANGE_FREQ:
            this.setMoveFrequency(params[0]);
            break;
        case gc.ROUTE_WALK_ANIME_ON:
            this.setWalkAnime(true);
            break;
        case gc.ROUTE_WALK_ANIME_OFF:
            this.setWalkAnime(false);
            break;
        case gc.ROUTE_STEP_ANIME_ON:
            this.setStepAnime(true);
            break;
        case gc.ROUTE_STEP_ANIME_OFF:
            this.setStepAnime(false);
            break;
        case gc.ROUTE_DIR_FIX_ON:
            this.setDirectionFix(true);
            break;
        case gc.ROUTE_DIR_FIX_OFF:
            this.setDirectionFix(false);
            break;
        case gc.ROUTE_THROUGH_ON:
            this.setThrough(true);
            break;
        case gc.ROUTE_THROUGH_OFF:
            this.setThrough(false);
            break;
        case gc.ROUTE_TRANSPARENT_ON:
            this.setTransparent(true);
            break;
        case gc.ROUTE_TRANSPARENT_OFF:
            this.setTransparent(false);
            break;
        case gc.ROUTE_CHANGE_IMAGE:
            this.setImage(params[0], params[1]);
            break;
        case gc.ROUTE_CHANGE_OPACITY:
            this.setOpacity(params[0]);
            break;
        case gc.ROUTE_CHANGE_BLEND_MODE:
            this.setBlendMode(params[0]);
            break;
        case gc.ROUTE_PLAY_SE:
            AudioManager.playSe(params[0]);
            break;
        case gc.ROUTE_SCRIPT:
            eval(params[0]);
            break;
        }
    }

    deltaXFrom(x) {
        return $gameMap.deltaX(this.x, x);
    }

    deltaYFrom(y) {
        return $gameMap.deltaY(this.y, y);
    }

    moveRandom() {
        let d = 2 + Math.round(Math.random()*4) * 2;
        if (this.canPass(this.x, this.y, d)) {
            this.moveStraight(d);
        }
    }

    moveTowardCharacter(character) {
        let sx = this.deltaXFrom(character.x);
        let sy = this.deltaYFrom(character.y);
        if (Math.abs(sx) > Math.abs(sy)) {
            this.moveStraight(sx > 0 ? 4 : 6);
            if (!this.isMovementSucceeded() && sy !== 0) {
                this.moveStraight(sy > 0 ? 8 : 2);
            }
        } else if (sy !== 0) {
            this.moveStraight(sy > 0 ? 8 : 2);
            if (!this.isMovementSucceeded() && sx !== 0) {
                this.moveStraight(sx > 0 ? 4 : 6);
            }
        }
    }

    moveAwayFromCharacter(character) {
        let sx = this.deltaXFrom(character.x);
        let sy = this.deltaYFrom(character.y);
        if (Math.abs(sx) > Math.abs(sy)) {
            this.moveStraight(sx > 0 ? 6 : 4);
            if (!this.isMovementSucceeded() && sy !== 0) {
                this.moveStraight(sy > 0 ? 2 : 8);
            }
        } else if (sy !== 0) {
            this.moveStraight(sy > 0 ? 2 : 8);
            if (!this.isMovementSucceeded() && sx !== 0) {
                this.moveStraight(sx > 0 ? 6 : 4);
            }
        }
    }

    turnTowardCharacter(character) {
        let sx = this.deltaXFrom(character.x);
        let sy = this.deltaYFrom(character.y);
        if (Math.abs(sx) > Math.abs(sy)) {
            this.setDirection(sx > 0 ? 4 : 6);
        } else if (sy !== 0) {
            this.setDirection(sy > 0 ? 8 : 2);
        }
    }

    turnAwayFromCharacter(character) {
        let sx = this.deltaXFrom(character.x);
        let sy = this.deltaYFrom(character.y);
        if (Math.abs(sx) > Math.abs(sy)) {
            this.setDirection(sx > 0 ? 6 : 4);
        } else if (sy !== 0) {
            this.setDirection(sy > 0 ? 2 : 8);
        }
    }

    turnTowardPlayer() {
        this.turnTowardCharacter($gamePlayer);
    }

    turnAwayFromPlayer() {
        this.turnAwayFromCharacter($gamePlayer);
    }

    moveTowardPlayer() {
        this.moveTowardCharacter($gamePlayer);
    }

    moveAwayFromPlayer() {
        this.moveAwayFromCharacter($gamePlayer);
    }

    moveForward() {
        this.moveStraight(this.direction());
    }

    moveBackward() {
        let lastDirectionFix = this.isDirectionFixed();
        this.setDirectionFix(true);
        this.moveStraight(this.reverseDir(this.direction()));
        this.setDirectionFix(lastDirectionFix);
    }

    processRouteEnd() {
        if (this._moveRoute.repeat) {
            this._moveRouteIndex = -1;
        } else if (this._moveRouteForcing) {
            this._moveRouteForcing = false;
            this.restoreMoveRoute();
        }
    }

    advanceMoveRouteIndex() {
        let moveRoute = this._moveRoute;
        if (moveRoute && (this.isMovementSucceeded() || moveRoute.skippable)) {
            let numCommands = moveRoute.list.length - 1;
            this._moveRouteIndex++;
            if (moveRoute.repeat && this._moveRouteIndex >= numCommands) {
                this._moveRouteIndex = 0;
            }
        }
    }

    turnRight90() {
        switch (this.direction()) {
        case 2:
            this.setDirection(4);
            break;
        case 4:
            this.setDirection(8);
            break;
        case 6:
            this.setDirection(2);
            break;
        case 8:
            this.setDirection(6);
            break;
        }
    }

    turnLeft90() {
        switch (this.direction()) {
        case 2:
            this.setDirection(6);
            break;
        case 4:
            this.setDirection(2);
            break;
        case 6:
            this.setDirection(8);
            break;
        case 8:
            this.setDirection(4);
            break;
        }
    }

    turn180() {
        this.setDirection(this.reverseDir(this.direction()));
    }

    turnRightOrLeft90() {
        switch (Math.round(Math.random()*2)) {
        case 0:
            this.turnRight90();
            break;
        case 1:
            this.turnLeft90();
            break;
        }
    }

    turnRandom() {
        this.setDirection(2 + Math.round(Math.random()*4) * 2);
    }

    swap(character) {
        let newX = character.x;
        let newY = character.y;
        character.locate(this.x, this.y);
        this.locate(newX, newY);
    }

    findDirectionTo(goalX, goalY) {
        let searchLimit = this.searchLimit();
        let mapWidth = $gameMap.width();
        let nodeList = [];
        let openList = [];
        let closedList = [];
        let start = {};
        let best = start;

        if (this.x == goalX && this.y == goalY) {
            return 0;
        }

        start.parent = null;
        start.x = this.x;
        start.y = this.y;
        start.g = 0;
        start.f = $gameMap.distance(start.x, start.y, goalX, goalY);
        nodeList.push(start);
        openList.push(start.y * mapWidth + start.x);

        while (nodeList.length > 0) {
            let bestIndex = 0;
            for (let i = 0; i < nodeList.length; i++) {
                if (nodeList[i].f < nodeList[bestIndex].f) {
                    bestIndex = i;
                }
            }

            let current = nodeList[bestIndex];
            let x1 = current.x;
            let y1 = current.y;
            let pos1 = y1 * mapWidth + x1;
            let g1 = current.g;

            nodeList.splice(bestIndex, 1);
            openList.splice(openList.indexOf(pos1), 1);
            closedList.push(pos1);

            if (current.x == goalX && current.y == goalY) {
                best = current;
                break;
            }

            if (g1 >= searchLimit) {
                continue;
            }

            for (let j = 0; j < 4; j++) {
                let direction = 2 + j * 2;
                let x2 = $gameMap.roundXWithDirection(x1, direction);
                let y2 = $gameMap.roundYWithDirection(y1, direction);
                let pos2 = y2 * mapWidth + x2;

                if (contains(closedList,pos2)) {
                    continue;
                }
                if (!this.canPass(x1, y1, direction)) {
                    continue;
                }

                let g2 = g1 + 1;
                let index2 = openList.indexOf(pos2);

                if (index2 < 0 || g2 < nodeList[index2].g) {
                    let neighbor;
                    if (index2 >= 0) {
                        neighbor = nodeList[index2];
                    } else {
                        neighbor = {};
                        nodeList.push(neighbor);
                        openList.push(pos2);
                    }
                    neighbor.parent = current;
                    neighbor.x = x2;
                    neighbor.y = y2;
                    neighbor.g = g2;
                    neighbor.f = g2 + $gameMap.distance(x2, y2, goalX, goalY);
                    if (!best || neighbor.f - neighbor.g < best.f - best.g) {
                        best = neighbor;
                    }
                }
            }
        }

        let node = best;
        while (node.parent && node.parent != start) {
            node = node.parent;
        }

        let deltaX1 = $gameMap.deltaX(node.x, start.x);
        let deltaY1 = $gameMap.deltaY(node.y, start.y);
        if (deltaY1 > 0) {
            return 2;
        } else if (deltaX1 < 0) {
            return 4;
        } else if (deltaX1 > 0) {
            return 6;
        } else if (deltaY1 < 0) {
            return 8;
        }

        let deltaX2 = this.deltaXFrom(goalX);
        let deltaY2 = this.deltaYFrom(goalY);
        if (Math.abs(deltaX2) > Math.abs(deltaY2)) {
            return deltaX2 > 0 ? 4 : 6;
        } else if (deltaY2 !== 0) {
            return deltaY2 > 0 ? 8 : 2;
        }

        return 0;
    }

    searchLimit() {
        return 12;
    }

    serialize(){
        return {
            ...super.serialize(),
            moveRouteForcing: this._moveRouteForcing,
            moveRoute: this._moveRoute,
            moveRouteIndex: this._moveRouteIndex,
            originalMoveRoute: this._originalMoveRoute,
            originalMoveRouteIndex: this._originalMoveRouteIndex,
            waitCount: this._waitCount
        }
    }

    deserialize(data){
        super.deserialize(data);
        this._moveRouteForcing = data.moveRouteForcing;
        this._moveRoute = data.moveRoute;
        this._moveRouteIndex = data.moveRouteIndex;
        this._originalMoveRoute = data.originalMoveRoute;
        this._originalMoveRouteIndex = data.originalMoveRouteIndex;
        this._waitCount = data.waitCount;
    }
}